/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tubes5progin.server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import tools.constants;
import tools.debug;
import tools.directoryHelper;
import tools.fileProcessor;
import tools.socketHelper;
import tubes5progin.client.clientSocketHandler.clientHandler;
import tubes5progin.client.folderstructure.filesystem;

/**
 *
 * @author Rezha
 */
public class serverHandler implements Runnable {

    //socket variable
    private Socket connection;
    private server connectionParent;
    private DataInputStream reader;
    private DataOutputStream writer;
    //client variable
    private String clientUsername;
    private filesystem fs;

    serverHandler(Socket server, server s) {
        this.connection = server;
        this.connectionParent = s;
        //dapatkan koneksi pertama dari client
    }

    private void createReaderWriter() {
        if (this.connection != null) {
            try {
                this.reader = socketHelper.getReader(this.connection);
                this.writer = socketHelper.getWriter(this.connection);
            } catch (Exception ex) {
                Logger.getLogger(serverHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void close() {
        try {
            this.connection.close();
            this.reader.close();
            this.writer.close();
        } catch (IOException ex) {
            Logger.getLogger(serverHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void run() {
        try {
            //disini dia menghandle protokol dari client yang connect
            //time out selama 10 detik
            this.connection.setSoTimeout(constants.timeout);
            createReaderWriter();
            if (this.reader == null || this.writer == null) {
                throw new Exception("Cannot create Reader and Writer, closing connection");
            } else {
                //dapatkan pesan pertama dari client
                String message = "";
                //pesan pertama, mestinya TUBES5PROGIN_<MODE>
                message = this.reader.readUTF();
                //handle client
                handleClient(message);
                connectionParent.currentConnection--;
            }
        } catch (SocketException ex) {
            debug.debugCode("Time Out Occured, Closing Connection");
            connectionParent.currentConnection--;
            close();
            Logger.getLogger(serverHandler.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            debug.debugCode("IO Exception Occured");
            connectionParent.currentConnection--;
            close();
            Logger.getLogger(serverHandler.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            debug.debugCode("Unhandled Exception Occured, We're Sorry");
            connectionParent.currentConnection--;
            close();
            Logger.getLogger(serverHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleClient(String message) throws SocketException, IOException, Exception {
        if (message.equals(constants.clientLoginMode)) {
            handleLogin();
        } else if (message.equals(constants.clientSyncMode)) {
            handleSync();
        } else if (message.equals(constants.clientDownloadMode)) {
            handleDownload();
        } else if (message.equals(constants.clientUploadMode)) {
            handleUpload();
        }
    }

    // <editor-fold defaultstate="collapsed" desc="CLIENT HANDLER">
    private void handleLogin() throws SocketException, IOException {
        try {
            //berikan balasan bahwa Server ready
            String reply = constants.serverReady;
            this.writer.writeUTF(reply);
            flush();
            String loginreply = this.reader.readUTF();
            String[] logreply = loginreply.split("#");
            SqlAccess.execute("SELECT * FROM userlogin WHERE username='" + logreply[0] + "' AND password='" + logreply[1] + "'");
            SqlAccess.getResult().last();
            if (SqlAccess.getResult().getRow() != 0) {
                //update login prop
                SqlAccess.executeUpdate("UPDATE userlogin SET islogin=1 WHERE username='" + logreply[0] + "';");
                //kirim berhasil login                
                SqlAccess.disconnect();
                //this.writer.writeUTF("Anda Berhasil Login");
                //flush();
            }
        } catch (Exception ex) {
            Logger.getLogger(serverHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.writer.writeUTF("Anda Gagal Login");
        flush();

    }

    private void handleSync() throws SocketException, IOException {
        //berikan balasan bahwa Server ready
        String reply = constants.serverReady;
        debug.debugCode("Mengirim Sync Mode Reply Kepada Client Berupa : " + reply);
        this.writer.writeUTF(reply);
        this.writer.flush();
        //tunggu balasan dari client berupa username
        String username = this.reader.readUTF();
        this.clientUsername = username;
        //cek apakah username tersebut sedang login
        if (!isLogin(this.clientUsername)) {
            //tutup koneksi
            //kirim bahwa client tidak bisa mengakses
            this.writer.writeUTF("Anda Belum Login");
            this.writer.flush();
            close();
            return;
        }
        //berarti client tersebut sedang login
        //kirim balasan bahwa username tersebut bisa mengirim data
        this.writer.writeUTF(constants.clientExist);
        this.writer.flush();

        // <editor-fold defaultstate="collapsed" desc="CONFIG LAMA">
        //tunggu balasan dari client berupa data file yang akan dikirim
        /*debug.debugCode("Menunggu Balasan dari Client berupa panjang File yang harus di download");
        int filelength = this.reader.readInt();
        debug.debugCode("Menerima Balasan dari client dengan panjang file : " + filelength);

        byte[] hasil = new byte[(int) filelength];
        //kirim balasan bahwa file siap untuk diterima
        this.writer.writeUTF(constants.readyToGetFile);
        this.writer.flush();
        //tunggu balasan dari server
        debug.debugCode("Menerima Byte dari Client");
        this.reader.read(hasil);*/


//trus tulis byte tersbut ke dalam sebuah file
        //directoryHelper.writeByteToFile(hasil, "object.data");
        //load ke dalam sebuah file system
        //filesystem fs = new filesystem();
        //fs.load(new File("object.data"));


        /*Object obj = directoryHelper.toObject(hasil);
        if(obj instanceof filesystem){
        fs = new filesystem((filesystem)obj);
        } else{
        debug.debugCode("FAILED....");
        }*/
        // </editor-fold>

        //cari panjang filesystem yang akan dikirim ke client
        filesystem fsServer = new filesystem(constants.rootDir + "\\" + this.clientUsername);
        byte[] fsByte = directoryHelper.toBytes(fsServer);
        int fsLengt = fsByte.length;
        //tunggu balasan bahwa client siap untuk menerima pesan
        String replyFromClient = this.reader.readUTF();
        debug.debugCode("Menerima Balasan dari Client : " + replyFromClient);
        if (replyFromClient.equals(constants.readyToGetFile)) {
            // ya udah gapapa, selese berarti
            // <editor-fold defaultstate="collapsed" desc="config lama 2">
/*debug.debugCode("Mengirim panjang byte ke client sepanjang: " + fsLengt);
            this.writer.writeInt(fsLengt);
            this.writer.flush();
            debug.debugCode("Mengirim Object ke Client");
            this.writer.write(fsByte);
            this.writer.flush();*/// </editor-fold>
        } else {
            //client tidak siap, tutup koneksi
            close();
        }
        debug.debugCode("testing saja kk");

    }

    private void handleDownload() throws SocketException, IOException, Exception {
        //kirim bahwa server siap untuk menerima koneksi
        this.writer.writeUTF(constants.serverReady);
        this.writer.flush();
        //menerima balasan dari client berupa username dari user
        String username = this.reader.readUTF();
        this.clientUsername = username;
        //cek dah login apa lum
        if (isLogin(clientUsername)) {
            //kirim balasan bahwa client exist
            filesystem fs2 = new directoryHelper(new File(constants.rootDir + "\\" + this.clientUsername)).getListFileSystem();
            byte[] fsByte = directoryHelper.toBytes(fs2);
            int length = fsByte.length;
            this.writer.writeUTF(constants.clientExist + "#" + length);
            this.writer.flush();
            //kirim filesystem yang ada buat si client
            this.writer.write(fsByte);
            //tunggu permintaan download dari si client
            //this.connection.setSoTimeout(0);
            while (true) {
                //terima bahwa si client siap download atau tidak
                String ready = this.reader.readUTF();
                if (ready.equals(constants.readyDownload)) {
                    //terima nama-nama file yang akan di download
                    String fileName = this.reader.readUTF();
                    while (!fileName.equals(constants.finishedDownload)) {
                        //cari file yang bernama filename
                        debug.debugCode("Mencari File dengan nama: " + fileName);
                        File temp = new File(constants.rootDir + "\\" + this.clientUsername + "\\" + fileName);
                        if (temp.isFile()) {
                            fileProcessor fp = new fileProcessor(temp);
                            byte[] hasil = fp.getByteFromFile();
                            if (hasil != null) {
                                int fileByte = hasil.length;
                                this.writer.writeInt(fileByte);
                                this.writer.flush();
                                //kirim bytenya
                                int count = fileByte / constants.maxSize;
                                if (fileByte % constants.maxSize != 0) {
                                    count++;
                                }
                                byte[] tempByte;
                                for (int i = 0; i < count; i++) {
                                    if ((i + 1) * constants.maxSize > fileByte) {
                                        tempByte = debug.getPartByte(hasil, i * constants.maxSize, fileByte - i * constants.maxSize);
                                    } else {
                                        tempByte = debug.getPartByte(hasil, i * constants.maxSize, constants.maxSize);
                                    }
                                    //delay(constants.delay);
                                    this.writer.write(tempByte);
                                    flush();
                                }
                                //this.writer.write(hasil);
                                //this.writer.flush();
                            } else {
                                this.writer.writeInt(-999);
                                this.writer.flush();
                            }

                        }
                        //baca dari client
                        fileName = this.reader.readUTF();
                    }
                    //keluar dari looping nyari file selesai mendownload
                    debug.debugCode("Client Selesai Mendownload, Berikan FileSystem Baru");
                    fs2 = new directoryHelper(new File(constants.rootDir + "\\" + this.clientUsername)).getListFileSystem();
                    fsByte = directoryHelper.toBytes(fs2);
                    debug.debugCode("Mengirim Panjang File Baru: " + fsByte.length);
                    length = fsByte.length;
                    this.writer.writeInt(length);
                    this.writer.flush();
                    this.writer.write(fsByte);
                    this.writer.flush();
                } else {
                    //kalo ga siap berarti kirim lagi filesystemnya
                    fs2 = new directoryHelper(new File(constants.rootDir + "\\" + this.clientUsername)).getListFileSystem();
                    fsByte = directoryHelper.toBytes(fs2);
                    length = fsByte.length;
                    this.writer.writeInt(length);
                    this.writer.flush();
                }

            }
        } else {
            close();
        }
    }

    public void flush() throws IOException {
        this.writer.flush();
    }

    private void handleUpload() throws SocketException, IOException, Exception {
        //kirim bahwa server siap untuk menerima koneksi
        filesystem fromClient;
        byte[] byteFromClient;
        int lengthFromClient;
        this.writer.writeUTF(constants.serverReady);
        this.writer.flush();
        //menerima balasan dari client berupa username dari user
        String username = this.reader.readUTF();
        int startidx = username.indexOf("#");
        lengthFromClient = Integer.parseInt(username.substring(startidx + 1));
        this.clientUsername = username.substring(0, startidx);
        //cek dah login apa lum
        if (isLogin(clientUsername)) {
            //kirim bahwa client exist
            this.writer.writeUTF(constants.clientExist);
            this.writer.flush();
            byteFromClient = new byte[lengthFromClient];
            //baca filesystem dari client
            this.reader.read(byteFromClient);
            fromClient = (filesystem) directoryHelper.toObject(byteFromClient);
            if (fromClient != null) {
                //dapatkan peredaan antara client dan server
                //filesystem fsServer = new filesystem(constants.rootDir + "\\" + this.clientUsername);
                File parentDir = new File(constants.rootDir + "\\" + this.clientUsername);
                directoryHelper dh = new directoryHelper(parentDir);
                filesystem fsServer = dh.getListFileSystem();
                filesystem diff = directoryHelper.getFileSystemDifferent(fromClient, fsServer);
                //buat dulu semua direktory yang ada di diff dan tidak ada di client
                while (true) {
                    if (diff.directory.isEmpty() && diff.files.isEmpty()) {
                        this.writer.writeUTF("asdkljasld");
                        this.writer.flush();
                        //tunggu balasan file system baru
                        lengthFromClient = this.reader.readInt();
                        byteFromClient = new byte[lengthFromClient];
                        this.reader.read(byteFromClient);
                        fromClient = (filesystem) directoryHelper.toObject(byteFromClient);
                        diff = directoryHelper.getFileSystemDifferent(fromClient, fsServer);

                    } else {
                        //bikin foldernya dulu kk
                        parentDir = new File(constants.rootDir + "\\" + this.clientUsername);
                        dh = new directoryHelper(parentDir);
                        dh.createAllDirs(diff, parentDir.getAbsolutePath());
                        //trus download semua filenya
                        debug.debugCode("Server kirim pesan daftar mau download");
                        this.writer.writeUTF(constants.readyDownload);
                        flush();
                        List<String> requiredFile = dh.getListFile(diff, "");
                        //kirim daftar file yang ingin di download
                        for (String s : requiredFile) {
                            this.writer.writeUTF(s);
                            flush();
                            //tahan beberapa detik
                            //delay(1000);
                            //tunggu panjang byte yang ingin dibuat
                            debug.debugCode("Mendownload File: " + s);
                            lengthFromClient = this.reader.readInt();
                            if (lengthFromClient > 0) {
                                int count = lengthFromClient / constants.maxSize;
                                if (lengthFromClient % constants.maxSize != 0) {
                                    count++;
                                }
                                byte[] temp;
                                byteFromClient = new byte[0];
                                for (int i = 0; i < count; i++) {
                                    //siap2 buat mendownload
                                    if ((i + 1) * constants.maxSize > lengthFromClient) {
                                        temp = new byte[lengthFromClient - i * constants.maxSize];
                                    } else {
                                        temp = new byte[constants.maxSize];
                                    }

                                    this.reader.read(temp);
                                    //tulis ke file
                                    byteFromClient = debug.appendByte(byteFromClient, temp);
                                }
                                fileProcessor fp = new fileProcessor(constants.rootDir + "\\" + this.clientUsername + "\\" + s);
                                fp.writeToFile(byteFromClient);

                            }
                        }
                        //kirim bahwa sudah selese nyari download
                        this.writer.writeUTF(constants.finishedDownload);
                        flush();
                        debug.debugCode("Selesai Mendownload");
                        //tunggu balasan file system baru
                        lengthFromClient = this.reader.readInt();
                        byteFromClient = new byte[lengthFromClient];
                        this.reader.read(byteFromClient);
                        fromClient = (filesystem) directoryHelper.toObject(byteFromClient);
                        dh = new directoryHelper(parentDir);
                        fsServer = dh.getListFileSystem();
                        if (fromClient != null) {
                            diff = directoryHelper.getFileSystemDifferent(fromClient, fsServer);
                        }
                    }
                }
            }

        } else {
            debug.debugCode("Maneh Belum Login");
            close();
        }

    }// </editor-fold>

    private boolean isLogin(String clientUsername) {
        try {
            SqlAccess.execute("SELECT * FROM userlogin WHERE username ='" + clientUsername + "' AND islogin=1");
            if (SqlAccess.getResult().next()) {
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            Logger.getLogger(serverHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        return false;
    }

    public void delay(int milisecond) {
        try {
            Thread.sleep(milisecond);

        } catch (InterruptedException ie) {
            System.out.println(ie.getMessage());
        }
    }
}
